﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


public class CorridorConnectionManager
{

    public Dictionary<int, int> TopKnownAvailable { get; private set; }
    public Dictionary<int, int> TopKnownUnavailable { get; private set; }
    public Dictionary<int, bool> TopUnknown { get; private set; }

    public Dictionary<int, int> BottomKnownAvailable { get; private set; }
    public Dictionary<int, int> BottomKnownUnavailable { get; private set; }
    public Dictionary<int, bool> BottomUnknown { get; private set; }

    public Dictionary<int, int> LeftKnownAvailable { get; private set; }
    public Dictionary<int, int> LeftKnownUnavailable { get; private set; }
    public Dictionary<int, bool> LeftUnknown { get; private set; }

    public Dictionary<int, int> RightKnownAvailable { get; private set; }
    public Dictionary<int, int> RightKnownUnavailable { get; private set; }
    public Dictionary<int, bool> RightUnknown { get; private set; }

    private DungeonPartition partition = null;

    public CorridorConnectionManager(DungeonPartition partition)
    {
        TopKnownAvailable = new Dictionary<int, int>();
        TopKnownUnavailable = new Dictionary<int, int>();
        TopUnknown = new Dictionary<int, bool>();
        BottomKnownAvailable = new Dictionary<int, int>();
        BottomKnownUnavailable = new Dictionary<int, int>();
        BottomUnknown = new Dictionary<int, bool>();
        LeftKnownAvailable = new Dictionary<int, int>();
        LeftKnownUnavailable = new Dictionary<int, int>();
        LeftUnknown = new Dictionary<int, bool>();
        RightKnownAvailable = new Dictionary<int, int>();
        RightKnownUnavailable = new Dictionary<int, int>();
        RightUnknown = new Dictionary<int, bool>();

        // First, mark all unknown. We'll reduce from these.
        for (int x = partition.X; x < partition.X + partition.Width; x++)
        {
            TopUnknown.Add(x, true);
            BottomUnknown.Add(x, true);
        }

        for (int y = partition.Y; y < partition.Y + partition.Height; y++)
        {
            LeftUnknown.Add(y, true);
            RightUnknown.Add(y, true);
        }

        this.partition = partition;
        if (partition.IsLeaf)
            InitializeLeaf();
        else
            InitializeParent();

    }

    private void CleanAvailabilityData()
    {

        // finally, clear unknown keys
        foreach (int x in TopKnownAvailable.Keys)
            TopUnknown.Remove(x);
        foreach (int x in TopKnownUnavailable.Keys)
            TopUnknown.Remove(x);
        foreach (int x in BottomKnownAvailable.Keys)
            BottomUnknown.Remove(x);
        foreach (int x in BottomKnownUnavailable.Keys)
            BottomUnknown.Remove(x);
        foreach (int y in LeftKnownAvailable.Keys)
            LeftUnknown.Remove(y);
        foreach (int y in LeftKnownUnavailable.Keys)
            LeftUnknown.Remove(y);
        foreach (int y in RightKnownAvailable.Keys)
            RightUnknown.Remove(y);
        foreach (int y in RightKnownUnavailable.Keys)
            RightUnknown.Remove(y);
    }

    private void InitializeParent()
    {
        CorridorConnectionManager leftManager = new CorridorConnectionManager(partition.Left);
        CorridorConnectionManager rightManager = new CorridorConnectionManager(partition.Right);

        switch (partition.Split)
        {
            case DungeonPartition.SplitType.Horizontal:

                // TODO: In a horizontal split, we have a top and bottom.
                // Top availability/unavailability inherits first from the top, second from the bottom.
                // Bottom availability/unavailability inherits first from the bottom, second from the top.
                // Left and right availability are a concatenation of both child partitions.
                // Then, left and right availability will be augmented via the new connection, where available (unknown).
                foreach (int topX in leftManager.TopKnownUnavailable.Keys)
                    this.TopKnownUnavailable.Add(topX, leftManager.TopKnownUnavailable[topX]);
                foreach (int bottomX in rightManager.BottomKnownUnavailable.Keys)
                    this.BottomKnownUnavailable.Add(bottomX, rightManager.BottomKnownUnavailable[bottomX]);
                foreach (int leftX in leftManager.LeftKnownUnavailable.Keys)
                    this.LeftKnownUnavailable.Add(leftX, leftManager.LeftKnownUnavailable[leftX]);
                foreach (int leftX in rightManager.LeftKnownUnavailable.Keys)
                    this.LeftKnownUnavailable.Add(leftX, rightManager.LeftKnownUnavailable[leftX]);
                foreach (int rightX in leftManager.RightKnownUnavailable.Keys)
                    this.RightKnownUnavailable.Add(rightX, leftManager.RightKnownUnavailable[rightX]);
                foreach (int rightX in rightManager.RightKnownUnavailable.Keys)
                    this.RightKnownUnavailable.Add(rightX, rightManager.RightKnownUnavailable[rightX]);

                foreach (int topX in leftManager.TopKnownAvailable.Keys)
                    this.TopKnownAvailable.Add(topX, leftManager.TopKnownAvailable[topX]);
                foreach (int bottomX in rightManager.BottomKnownAvailable.Keys)
                    this.BottomKnownAvailable.Add(bottomX, rightManager.BottomKnownAvailable[bottomX]);
                foreach (int leftX in leftManager.LeftKnownAvailable.Keys)
                    this.LeftKnownAvailable.Add(leftX, leftManager.LeftKnownAvailable[leftX]);
                foreach (int leftX in rightManager.LeftKnownAvailable.Keys)
                    this.LeftKnownAvailable.Add(leftX, rightManager.LeftKnownAvailable[leftX]);
                foreach (int rightX in leftManager.RightKnownAvailable.Keys)
                    this.RightKnownAvailable.Add(rightX, leftManager.RightKnownAvailable[rightX]);
                foreach (int rightX in rightManager.RightKnownAvailable.Keys)
                    this.RightKnownAvailable.Add(rightX, rightManager.RightKnownAvailable[rightX]);

                foreach (int topX in rightManager.TopKnownUnavailable.Keys)
                    if (!this.TopKnownUnavailable.ContainsKey(topX) && !this.TopKnownAvailable.ContainsKey(topX))
                        this.TopKnownUnavailable.Add(topX, rightManager.TopKnownUnavailable[topX]);
                foreach (int bottomX in leftManager.BottomKnownUnavailable.Keys)
                    if (!this.BottomKnownUnavailable.ContainsKey(bottomX) && !this.BottomKnownAvailable.ContainsKey(bottomX))
                        this.BottomKnownUnavailable.Add(bottomX, leftManager.BottomKnownUnavailable[bottomX]);
                foreach (int topX in rightManager.TopKnownAvailable.Keys)
                    if (!this.TopKnownAvailable.ContainsKey(topX) && !this.TopKnownUnavailable.ContainsKey(topX))
                        this.TopKnownAvailable.Add(topX, rightManager.TopKnownAvailable[topX]);
                foreach (int bottomX in leftManager.BottomKnownAvailable.Keys)
                    if (!this.BottomKnownAvailable.ContainsKey(bottomX) && !this.BottomKnownUnavailable.ContainsKey(bottomX))
                        this.BottomKnownAvailable.Add(bottomX, leftManager.BottomKnownAvailable[bottomX]);

                CleanAvailabilityData();


                int corridorX = CorridorConnectionManager.FindTopBottomConnection(leftManager, rightManager);
                int corridorTopY = leftManager.BottomKnownAvailable[corridorX] + 1;
                int corridorBottomY = rightManager.TopKnownAvailable[corridorX] - 1;

                Corridor newCorridor = new Corridor(Corridor.CorridorType.Vertical, corridorTopY, corridorBottomY, corridorX);
                partition.Corridor = newCorridor;

                // TODO: Update left/right availability based on the new corridor
                for (int y = corridorTopY; y <= corridorBottomY; y++)
                {
                    if (this.LeftUnknown.ContainsKey(y))
                    {
                        this.LeftKnownAvailable.Add(y, corridorX);
                        this.LeftUnknown.Remove(y);
                    }
                    if (this.RightUnknown.ContainsKey(y))
                    {
                        this.RightKnownAvailable.Add(y, corridorX);
                        this.RightUnknown.Remove(y);
                    }

                    // Edge cases - when the corridor exactly overlaps an unvailable square, it becomes available.
                    if (this.LeftKnownUnavailable.ContainsKey(y) && this.LeftKnownUnavailable[y] == corridorX)
                    {
                        LeftKnownUnavailable.Remove(y);
                        LeftKnownAvailable.Add(y, corridorX);
                    }
                    if (this.RightKnownUnavailable.ContainsKey(y) && this.RightKnownUnavailable[y] == corridorX)
                    {
                        RightKnownUnavailable.Remove(y);
                        RightKnownAvailable.Add(y, corridorX);
                    }

                    // Other edge cases - when the corridor trumps an available square, the value is updated.
                    if (this.LeftKnownAvailable.ContainsKey(y) && this.LeftKnownAvailable[y] > corridorX)
                        this.LeftKnownAvailable[y] = corridorX;
                    if (this.RightKnownAvailable.ContainsKey(y) && this.RightKnownAvailable[y] < corridorX)
                        this.RightKnownAvailable[y] = corridorX;
                }
                break;
            case DungeonPartition.SplitType.Vertical:

                // TODO: In a vertical split, we have a left and right.
                // Left availability/unavailability inherits first from the left, second from the right.
                // Right availability/unavailability inherits first from the right, second from the left.
                // Top and bottom availability are a concatenation of both child partitions.
                // Then, top and bottom availability will be augmented via the new connection, where available (unknown).
                foreach (int leftX in leftManager.LeftKnownUnavailable.Keys)
                    this.LeftKnownUnavailable.Add(leftX, leftManager.LeftKnownUnavailable[leftX]);
                foreach (int rightX in rightManager.RightKnownUnavailable.Keys)
                    this.RightKnownUnavailable.Add(rightX, rightManager.RightKnownUnavailable[rightX]);
                foreach (int t_X in leftManager.TopKnownUnavailable.Keys)
                    this.TopKnownUnavailable.Add(t_X, leftManager.TopKnownUnavailable[t_X]);
                foreach (int t_X in rightManager.TopKnownUnavailable.Keys)
                    this.TopKnownUnavailable.Add(t_X, rightManager.TopKnownUnavailable[t_X]);
                foreach (int b_X in leftManager.BottomKnownUnavailable.Keys)
                    this.BottomKnownUnavailable.Add(b_X, leftManager.BottomKnownUnavailable[b_X]);
                foreach (int b_X in rightManager.BottomKnownUnavailable.Keys)
                    this.BottomKnownUnavailable.Add(b_X, rightManager.BottomKnownUnavailable[b_X]);

                foreach (int leftX in leftManager.LeftKnownAvailable.Keys)
                    this.LeftKnownAvailable.Add(leftX, leftManager.LeftKnownAvailable[leftX]);
                foreach (int rightX in rightManager.RightKnownAvailable.Keys)
                    this.RightKnownAvailable.Add(rightX, rightManager.RightKnownAvailable[rightX]);
                foreach (int t_X in leftManager.TopKnownAvailable.Keys)
                    this.TopKnownAvailable.Add(t_X, leftManager.TopKnownAvailable[t_X]);
                foreach (int t_X in rightManager.TopKnownAvailable.Keys)
                    this.TopKnownAvailable.Add(t_X, rightManager.TopKnownAvailable[t_X]);
                foreach (int b_X in leftManager.BottomKnownAvailable.Keys)
                    this.BottomKnownAvailable.Add(b_X, leftManager.BottomKnownAvailable[b_X]);
                foreach (int b_X in rightManager.BottomKnownAvailable.Keys)
                    this.BottomKnownAvailable.Add(b_X, rightManager.BottomKnownAvailable[b_X]);

                foreach (int leftX in rightManager.LeftKnownUnavailable.Keys)
                    if (!this.LeftKnownUnavailable.ContainsKey(leftX) && !this.LeftKnownAvailable.ContainsKey(leftX))
                        this.LeftKnownUnavailable.Add(leftX, rightManager.LeftKnownUnavailable[leftX]);
                foreach (int rightX in leftManager.RightKnownUnavailable.Keys)
                    if (!this.RightKnownUnavailable.ContainsKey(rightX) && !this.RightKnownAvailable.ContainsKey(rightX))
                        this.RightKnownUnavailable.Add(rightX, leftManager.RightKnownUnavailable[rightX]);
                foreach (int leftX in rightManager.LeftKnownAvailable.Keys)
                    if (!this.LeftKnownAvailable.ContainsKey(leftX) && !this.LeftKnownUnavailable.ContainsKey(leftX))
                        this.LeftKnownAvailable.Add(leftX, rightManager.LeftKnownAvailable[leftX]);
                foreach (int rightX in leftManager.RightKnownAvailable.Keys)
                    if (!this.RightKnownAvailable.ContainsKey(rightX) && !this.RightKnownUnavailable.ContainsKey(rightX))
                        this.RightKnownAvailable.Add(rightX, leftManager.RightKnownAvailable[rightX]);

                CleanAvailabilityData();


                int corridorY = CorridorConnectionManager.FindLeftRightConnection(leftManager, rightManager);
                int corridorLeftX = leftManager.RightKnownAvailable[corridorY] + 1;
                int corridorRightX = rightManager.LeftKnownAvailable[corridorY] - 1;

                Corridor newHorizontalCorridor = new Corridor(Corridor.CorridorType.Horizontal, corridorLeftX, corridorRightX, corridorY);
                partition.Corridor = newHorizontalCorridor;

                // Update top/bottom availability based on the new corridor
                for (int x = corridorLeftX; x <= corridorRightX; x++)
                {
                    if (this.TopUnknown.ContainsKey(x))
                    {
                        this.TopKnownAvailable.Add(x, corridorY);
                        this.TopUnknown.Remove(x);
                    }
                    if (this.BottomUnknown.ContainsKey(x))
                    {
                        this.BottomKnownAvailable.Add(x, corridorY);
                        this.BottomUnknown.Remove(x);
                    }

                    // Edge cases - when the corridor exactly overlaps an unvailable square, it becomes available.
                    if (this.TopKnownUnavailable.ContainsKey(x) && this.TopKnownUnavailable[x] == corridorY)
                    {
                        TopKnownUnavailable.Remove(x);
                        TopKnownAvailable.Add(x, corridorY);
                    }
                    if (this.BottomKnownUnavailable.ContainsKey(x) && this.BottomKnownUnavailable[x] == corridorY)
                    {
                        BottomKnownUnavailable.Remove(x);
                        BottomKnownAvailable.Add(x, corridorY);
                    }

                    // Other edge cases - when the corridor trumps an available square, the value is updated.
                    if (this.TopKnownAvailable.ContainsKey(x) && this.TopKnownAvailable[x] > corridorY)
                        this.TopKnownAvailable[x] = corridorY;
                    if (this.BottomKnownAvailable.ContainsKey(x) && this.BottomKnownAvailable[x] < corridorY)
                        this.BottomKnownAvailable[x] = corridorY;
                }
                
                break;
        }
    }

    private void InitializeLeaf()
    {
        // When initializing a leaf, we use the room inside it to determine points.

        // Room position is in absolute coordinates. We also want to store in absolute coordinates.

        int roomX = partition.Room.X;
        int roomY = partition.Room.Y;
        int roomWidth = partition.Room.Width;
        int roomHeight = partition.Room.Height;



        // Second, mark known available based on the room.
        for (int x = roomX; x < roomX + roomWidth; x++)
        {
            TopKnownAvailable.Add(x, roomY);
            BottomKnownAvailable.Add(x, roomY + roomHeight - 1);
        }

        for (int y = roomY; y < roomY + roomHeight; y++)
        {
            LeftKnownAvailable.Add(y, roomX);
            RightKnownAvailable.Add(y, roomX + roomWidth - 1);
        }


        // Third, mark known unavailable based on the room.
        if (roomX > partition.X)
        {
            TopKnownUnavailable.Add(roomX - 1, roomY);
            BottomKnownUnavailable.Add(roomX - 1, roomY + roomHeight - 1);
        }
        if (roomX + roomWidth < partition.X + partition.Width)
        {
            TopKnownUnavailable.Add(roomX + roomWidth, roomY);
            BottomKnownUnavailable.Add(roomX + roomWidth, roomY + roomHeight - 1);
        }
        if (roomY > partition.Y)
        {
            LeftKnownUnavailable.Add(roomY - 1, roomX);
            RightKnownUnavailable.Add(roomY - 1, roomX + roomWidth - 1);
        }
        if (roomY + roomHeight < partition.Y + partition.Height)
        {
            LeftKnownUnavailable.Add(roomY + roomHeight, roomX);
            RightKnownUnavailable.Add(roomY + roomHeight, roomX + roomWidth - 1);
        }

        CleanAvailabilityData();
    }

    public static int FindTopBottomConnection(CorridorConnectionManager topPartitionManager, CorridorConnectionManager bottomPartitionManager)
    {
        List<int> commonValues = new List<int>();
        foreach (int key in topPartitionManager.BottomKnownAvailable.Keys)
            if (bottomPartitionManager.TopKnownAvailable.Keys.Contains(key))
                commonValues.Add(key);

        if (commonValues.Count == 0) throw new ApplicationException("We can't find any top-bottom available connection, which should be impossible.");

        return commonValues[DungeonPartition.randomizer.Next(0, commonValues.Count - 1)];
    }

    public static int FindLeftRightConnection(CorridorConnectionManager leftPartitionManager, CorridorConnectionManager rightPartitionManager)
    {
        List<int> commonValues = new List<int>();
        foreach (int key in leftPartitionManager.RightKnownAvailable.Keys)
            if (rightPartitionManager.LeftKnownAvailable.Keys.Contains(key))
                commonValues.Add(key);

        if (commonValues.Count == 0) throw new ApplicationException("We can't find any left-right available connection, which should be impossible.");

        return commonValues[DungeonPartition.randomizer.Next(0, commonValues.Count - 1)];
    }

}
